#新的想法，并没有根据各平台加入基于客户提供的数据集自动生成测试数据，或者客户可以自己上传测试数据集，
#方案解析：现在大多的平台自带RAG系统，但是对于自己的RAG系统有多大的提升，包括准确率，和性能方面，而且可以在工作流编排的
#可以加入自己的检索API，进行精度验证，独特的点就是，基于自己的数据集，基于自己构建的RAG系统进行快速精度验证，检索的速度；
#客户只需要输入query和标准的ground truth 即可
#针对不同的输入数据进行不同的处理方式
#工作流程：（1）文档——》加载处理（从-naive 到 advance 再到 deepdoc）
#（2）文档 —— 》 分割
#（3）文档 ——》存储 （从 向量 ——》 文本）（是否加入微调embdding）
# (4) 文档检索 是否加入rerank进行排序
# （5） 答案生成 （更换模型，是否需要对模型进行微调）
#（6） 进行评估精度和速度
# （7）是否需要将评估的数据加入平台进行自动评估，自动生成数据集，自动进行文档检索的的能力进行评估
import sys
import json
# import faiss
# import pymilvus
import string
import os 
# 添加项目根目录到系统路径
sys.path.append('/home/data/home/wct')
from backend.llm import  llm_server
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings,SentenceTransformerEmbeddings
from langchain_community.vectorstores import FAISS
import requests
import json
from typing import List, Optional, Any, Dict
prompt = """
你是一个专业的本地知识库问答助手，你需要基于以下【检索到的上下文】，回答用户提出的【问题】。请严格遵循以下规则：

1. 回答必须完全基于【检索到的上下文】中的信息，不得引入任何外部知识（即使你已知相关内容）。若上下文未覆盖问题中的信息，直接说明“根据提供的信息，无法回答该问题”。

2. 优先使用上下文中原话或核心表述，如需概括需保证与原文含义一致，避免添加主观解读。

3. 结构清晰：先直接回应问题核心，再分点补充细节（若信息较多），最后可简要总结（可选）。

4. 若上下文包含多个相关片段，需整合所有相关信息作答，避免遗漏关键内容。


【检索到的上下文】：
{context}


【问题】：
{question}
"""
# faithfullness_prompt = f"""
#     任务：判断答案是否完全基于提供的上下文，无任何外部信息或编造内容。
#     上下文：
#     {chr(10).join([f"[上下文片段{i+1}]：{ctx}" for i, ctx in enumerate(contexts)])}  # 按片段标注，便于定位
    
#     答案：{answer}
    
#     评估标准：
#     1. 拆解答案中的所有事实性陈述（如定义、参数、步骤、结论等）。
#     2. 检查每个事实是否能在上下文的至少一个片段中找到明确依据（允许同义转述，但不允许新增信息）。
#     3. 若所有事实均有依据，输出1.0；若存在任何无依据的事实，输出0.0。
    
#     只输出一个数字（1.0或0.0），不附加任何解释。
#     """

def docparse(dir_path:str):
    all_docs = []
    for path  in os.listdir(dir_path):
        path  = dir_path +"/" +  path
        print(path)
        loader = PyPDFLoader(file_path = path)
        data = loader.load()
        text_spliter = CharacterTextSplitter(separator = "\n\n",chunk_size=1024,chunk_overlap=100)
        data = text_spliter.split_documents(data)
        all_docs.extend(data)

    db_path = "./rag_database"
    embeddings =  HuggingFaceEmbeddings(model_name="/home/data/home/Jerry0/text2vec-base-chinese")
    if os.path.exists(db_path) and os.listdir(db_path):
            # 加载现有数据库并追加
        db = FAISS.load_local(db_path, embeddings,allow_dangerous_deserialization = True)
        db.add_documents(all_docs)
    else:
        # 创建新数据库
        db = FAISS.from_documents(all_docs, embeddings,allow_dangerous_deserialization = True)
    db.save_local(db_path)
    print(f"向量数据库已保存至 {db_path}")
    return db
    # retriever = db.as_retriever(search_type="mmr")
    # query = "什么是mx-smi工具"
    # docs = retriever.get_relevant_documents(query)
    
def RAGgenerate(dataset_path: str,retriever):
    with open(dataset_path,"r",encoding= "utf-8") as f:
            dataset_json = json.load(f)
            context_all = []
            response_all = []
            # print(len(dataset_json))
            for i in range(len(dataset_json)):
                query = dataset_json[i]["question"]
                docs = retriever.get_relevant_documents(query)
                context = []
                j = 0
                for doc in docs:
                    processed_text = doc.page_content.replace("\n", " ").strip()
                    j+=1
                    # 处理连续空格（可选，让文本更整洁）
                    import re
                    page_content = re.sub(r'\s+', ' ', processed_text)
                    page_content = "片段{}.".format(j) + page_content
                    context.append(page_content)
                    import time
                    time.sleep(1)
                context_all.append(context)
                query = prompt.replace("context",str(context)).replace("question",query)
                response = llm_server(query)[1]
                response_all.append(response)
            print("context",context_all[0])
            print("anser",response_all[0])
            
            test_dataset = []
            for i in range(len(dataset_json)):
                text_json = {
                "query": "",
                "context": [],
                "amswer": "",
                "response": "",
            }
                text_json["query"] = dataset_json[i]["question"]
                text_json["context"] = context_all[i]
                text_json["amswer"] = dataset_json[i]["answer"]
                text_json["response"] = response_all[i]
                test_dataset.append(text_json)
            return test_dataset
import pandas as pd  # 需导入pandas用于表格处理

def evaluate(dataset_list: list, output_csv_path: str = None):
    """
    评估数据集并将结果保存为CSV表格
    
    参数:
    - dataset_list: 待评估的数据列表
    - output_csv_path: 保存CSV的路径（可选），若提供则保存表格
    
    返回:
    - result_df: 评估结果的DataFrame
    - overall_average: 所有数据的平均分数
    """
    # 用于存储所有数据的评估结果（每条数据对应一行）
    evaluation_results = []
    length_dataset = len(dataset_list)
    
    for i in range(length_dataset):
        # 1. 提取当前数据的原始信息
        query = dataset_list[i]["query"]
        contexts = dataset_list[i]["context"]
        ground_truth = dataset_list[i]["amswer"]  # 注意：建议修正拼写为"answer"
        response = dataset_list[i]["response"]
        
        # 2. 定义评估函数（保持原有逻辑）
        def faithfulness(response, contexts) -> float:
            prompt = f"""
                角色：你是一个本地知识库的评估专家；
                任务：判断答案是否完全基于提供的上下文，无任何外部信息或编造内容。
                上下文：
                {str(contexts)}
                
                答案：{response}
                
                评估标准：
                1. 拆解答案中的所有事实性陈述（如定义、参数、步骤、结论等）。
                2. 检查每个事实是否能在上下文的至少一个片段中找到明确依据（允许同义转述，但不允许新增信息）。
                3. 若所有事实均有依据，输出1.0；若存在任何无依据的事实，输出0.0。
                
                只输出一个数字（1.0或0.0），不附加任何解释。
                """
            try:
                return float(llm_server(prompt)[1].strip())
            except:
                return 0.0
        
        def answer_relevance(query, response) -> float:
            prompt = f"""
                角色：你是一个本地知识库的评估专家；
                任务：评估答案与问题的相关程度，输出0-1之间的分数（保留1位小数）。
                问题：{query}
                答案：{response}
                
                评分标准（严格按以下规则打分）：
                - 1.0：答案直接回应问题核心，无冗余内容，且覆盖问题所需的全部信息维度
                - 0.8：答案回应问题核心，但存在少量无关内容（不影响理解）
                - 0.5：答案部分相关，但未完全覆盖问题核心（如只回答了部分疑问）
                - 0.3：答案与问题有微弱关联（如仅提及问题中的个别关键词，但未回应核心）
                - 0.0：答案与问题完全无关
                
                只输出一个数字（如1.0、0.5），不附加任何解释。
                """
            try:
                return float(llm_server(prompt)[1].strip())
            except:
                return 0.5
        
        def context_precision(query ,contexts, top_k: int = 3) -> float:
            prompt = f"""
            任务：判断上下文是否与问题相关（直接支持问题解答），输出1（相关）或0（无关）。
            问题：{query}
            上下文：{{ctx}}
            
            相关标准：
            - 相关（1）：上下文包含回答问题所需的直接信息（如定义、数据、步骤等）
            - 无关（0）：上下文未提及与问题相关的任何信息，或仅提及无关细节
            
            只输出1或0，不附加任何解释。
            """
            weighted_relevance = 0.0
            for idx, ctx in enumerate(contexts[:top_k], 1):
                prompt = prompt.format(ctx=ctx)
                try:
                    relevance = float(llm_server(prompt)[1].strip())
                    weighted_relevance += relevance * (1 / idx)
                except:
                    weighted_relevance += 0.0
            max_possible_score = sum(1 / idx for idx in range(1, top_k + 1))
            return weighted_relevance / max_possible_score if max_possible_score > 0 else 0.0
        
        def context_recall(contexts, ground_truth) -> float:
            prompt = f"""
                任务：判断检索到的上下文是否包含标准答案所需的全部关键信息。

                标准答案依赖的关键信息：
                {ground_truth}

                检索到的上下文片段（用###分隔）：
                {str(contexts)}

                评分标准：
                - 1：所有关键信息均被至少一个上下文片段完整包含（无遗漏）。
                - 0：存在任何一条关键信息未被任何上下文片段包含。

                请仅输出0.0 到 1.0 之间的匹配相关程度分数，不附加任何解释。
                """
            try:
                return float(llm_server(prompt)[1].strip())
            except:
                return 0.0
        
        # 3. 计算当前数据的各项指标分数
        faithfulness_score = faithfulness(response, contexts)
        relevance_score = answer_relevance(query, response)
        precision_score = context_precision(query, contexts)
        recall_score = context_recall(contexts, ground_truth)
        avg_score = (faithfulness_score + relevance_score + precision_score + recall_score) / 4
        
        # 4. 将当前数据的原始信息和分数存入结果列表（一行数据）
        evaluation_results.append({
            # 原始数据列
            "数据索引": i,  # 标记是第几条数据
            "问题（query）": query,
            "上下文（context）": str(contexts),  # 转为字符串便于表格显示
            "标准答案（ground_truth）": ground_truth,
            "模型回答（response）": response,
            # 评估指标列
            "忠实度（faithfulness）": faithfulness_score,  # 答案是否完全基于上下文
            "答案相关性（answer_relevance）": relevance_score,  # 回答与问题的相关度
            "上下文精准度（context_precision）": precision_score,  # 上下文与问题的相关度
            "上下文召回率（context_recall）": recall_score,  # 上下文是否覆盖关键信息
            "四项指标平均分": avg_score
        })
    
    # 5. 将结果列表转换为DataFrame表格
    result_df = pd.DataFrame(evaluation_results)
    
    # 6. 计算所有数据的整体平均分
    overall_average = result_df["四项指标平均分"].mean()
    
    # 7. 保存为CSV（如果指定了路径）
    if output_csv_path:
        result_df.to_csv(output_csv_path, index=False, encoding='utf-8-sig')  # 用utf-8-sig确保中文不乱码
        print(f"✅ 评估结果已保存至: {output_csv_path}")
    
    # 返回表格和总平均分
    return result_df, overall_average
if __name__== "__main__":
    # muxi_dir  = "/home/data/home/RAG文档/pdf/muxi"
    # dataset = "/home/data/home/wct/RAGAS/muxi.json"
    # db = docparse(muxi_dir)
    # retriever = db.as_retriever(search_type="mmr",search_kwargs={"k": 3})
    # dataset_list = RAGgenerate(dataset,retriever)
    # with open("./dataset.json","w",encoding="utf-8") as f:
    #     json.dump(dataset_list, f, indent=2, ensure_ascii=False)
    # total_score = evaluate(dataset_list)
    # print("最后的总得分是多少",total_score)
    # query = "什么是mx-smi工具"
    # docs = retriever.get_relevant_documents(query)
    # print(docs)
    with open("./dataset.json","r",encoding="utf-8") as f:
        dataset_list = json.load(f)
        result_table, total_avg = evaluate(dataset_list, "评估结果.csv")